/**
 * Implements a collection of 'type checking' functions useful for verifying things about
 *   an object or function.
 */

(function(window) {
	
	/**
	 * @class Types
	 */

	var types = {};

	/**
	 * add a symbol to the global namespace. 
	 *  For example, the following code will define TestObject in a closure
	 *  and add it to the global namespace as: js.mypackage.TestObject
	 
		(function() {
			var TestObject = {
				foo:'bar'
			}
			Types.exportSymbol("js.mypackage.TestObject", TestObject);
		})();
	
	 * @memberOf Types
	 * @name exportSymbol
	 */
	types.exportSymbol = function exportSymbol(name, target) {
		var parts = name.split('.');
		
		var object = window;
		for(var i = 0; i < parts.length - 1; i++) {
			var part = parts[i];
			var next = object[part] || {};
			object[part] = next;
			object = next;
		}
		var part = parts[parts.length - 1];
		object[part] = target;
	};
	
	/**
	 * Similar to java's import statement.
	 *  eg: Types.importSymbol("js.com.sandwormz.test.Test");
	 * 
	 *  Test.assertEquals(this, that, "this and that must be identical.");
	 * 
	 * instead of:
	 * 
	 * js.com.sandwormz.test.Test.assertEquals(this, that, "this and that must be identical.");
	 * 
	 * @memberOf Types#
	 * @name importSymbol
	 * @expose 
	 */
	types.importSymbol = function(name) {
		//if(! importTarget) {
		//	importTarget = window;
		//}
		var parts = name.split('.');
		
		var object = window;
		for(var i = 0; i < parts.length; i++) {
			var part = parts[i];
			object = object[part];
		}
		//importTarget[parts[parts.length - 1]] = object;
		return object;
	};
	
	/**
	 * Export a symbol with the given name and target, and declare it 
	 *   an interface.
	 * 
	 * Objects with the same methods and argument counts will return 
	 *   true for Object.doesImplement
	 */
	types.exportInterface = function exportInterface(name, target) {
		types.declareStubMethods(target);
		types.exportSymbol(name, target);
	}
	
	/**
	 * Marks all methods declared in the object to be "stub" methods.
	 *   Attempts to call these methods will throw exceptions and
	 *   they will be marked as stubs, along with their original 
	 *   arguments for use by the exportInterface.
	 * 
	 * These methods will have the following properties added to them:
	 *   _original: the original method before it was stubbed out.
	 *   _stub: true a flag indicating that this method is a stub. 
	 */
	types.declareStubMethods = function(object) {
		for(name in object) {
			if(object.hasOwnProperty(name)) {
				//don't stub out inherited methods
				var func =  _getErrorFunction(name);
				func['_original'] = object[name];
				object[name] = func;
			}
		}
			
		//forces the real name to be set, define in a loop and they
		//all get the same function name: the last one.
		function _getErrorFunction(forName) {
			var stubMethod = function() {
				Assert.fail("The method '" + forName + "' has not been defined.")
			}
			stubMethod['_stub'] = true;
			
			return stubMethod
		}
	}
	
	/**
	 * Returns true if the specified object has any stub methods in it.
	 *   Useful if you wish to declare your methods at the top of a class
	 *   and implement them elsewhere.
	 */
	types.hasStubMethods = function(object) {
		for(var name in object) {
			if(object.hasOwnProperty(name)) {
				var thisProperty = object[name];
				if(typeof thisProperty === 'function') {
					if(thisProperty['_stub']) {
						return true;
					}
				}
			}
		}
		return false;
	}
	
	/**
	 * Returns an array of all the methods in the specified interface not implemented 
	 * 	by the interface
	 */
	types.getUnimplementedMethods = function(thisObject, interfaceObject) {
		Assert.assertNotNull(interfaceObject);
		
		var unimplementedMethods = [];
		for(var property in interfaceObject) {
			if(!interfaceObject.hasOwnProperty(property)) {
				continue;
			}
			var ifProperty = interfaceObject[property];
			var thisProperty = thisObject[property];
			if(ifProperty['_original']) {
				ifProperty = ifProperty['_original']
			}
			if(typeof ifProperty === 'function') {
				if(typeof thisProperty === 'function') {
					if(thisProperty['_stub']) {
						unimplementedMethods.push(property + " is a stub method.");
					} else if(thisProperty.length != ifProperty.length) {
						unimplementedMethods.push(property + " has wrong number of arguments: expected " + ifProperty.length + " has " + thisProperty.length);
					}
				} else {
					unimplementedMethods.push(property + " isn't present.");
				}
			}
		}
		return unimplementedMethods;
	}
	
	types.implementsMethod = function(object, methodName, argumentCount) {
		argumentCount = Utilities.valueOrDefault(argumentCount, -1);
		if(typeof this[methodName] === 'function') {
			var method = object[methodName];
			if(method['_stub']) {
				return false
			}
			return true;
		} else {
			return false;
		}		
	}
	
	/*
	Object.prototype['implementsMethod'] = function(methodName, argumentCount) {
		return types.implementsMethod(this, methodName, argumentCount);
	}
	*/
	
	/**
	 * Given an interface (which is just an object 
	 * 	  with properties and methods (which may be empty)) this
	 *    will return true if this object implements each 
	 *    method in the interface
	 * 
     * @param {Object} interfaceObject
	 */
	
	types.doesImplement = function(object, interfaceObject) {
		if(! interfaceObject) {
			return false;
		}
		var unimplementedMethods = types.getUnimplementedMethods(object, interfaceObject);
		return unimplementedMethods.length == 0;		
	}
	
	/*
	Object.prototype['doesImplement'] = function(interfaceObject) {
		return types.doesImplement(this, interfaceObject);
	}
	*/
	
	types.exportSymbol("Types", types);
	
})(this);

